package com.lzz.log.helper;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.TimeUnit;

@WebFilter(filterName="logFilter",urlPatterns="/*")
@Order(Ordered.LOWEST_PRECEDENCE)
@Component
public class LogFilter implements Filter {

    protected Logger log = LoggerFactory.getLogger(getClass());
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @SneakyThrows
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

        //创建ContentCachingRequestWrapper对象用于缓存请求体
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(httpServletRequest);
        //创建ContentCachingResponseWrapper对象用于缓存响应体
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpServletResponse);
        long tookMs = 0;
        try {

            // 打印执行时间
            long startNs = System.nanoTime();
            //继续执行过滤器链，并传递包装后的请求对象
            filterChain.doFilter(requestWrapper, responseWrapper);
            tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
            printRequestLog(requestWrapper);
            printResponseLog(requestWrapper, responseWrapper,tookMs);
        }finally {
            //将缓存的响应体数据回写到响应流中
            responseWrapper.copyBodyToResponse();
        }
    }


    private void printRequestLog( ContentCachingRequestWrapper requestWrapper) {
        StringBuilder beforeReqLog = new StringBuilder(200);
        // 日志参数
        List<Object> beforeReqArgs = new ArrayList<>();
        try {
            beforeReqLog.append("\n============================  Request  ============================\n");
            // 打印路由
            beforeReqLog.append("===> {}: {}\n");
            beforeReqArgs.add(requestWrapper.getMethod());
            beforeReqArgs.add(requestWrapper.getRequestURI());

            if(StrUtil.isNotBlank(requestWrapper.getQueryString())){
                beforeReqLog.append("===> Parameters: {}\n");
                beforeReqArgs.add(requestWrapper.getQueryString());
            }

            if (!isMultipart(requestWrapper) && !isBinaryContent(requestWrapper)) {
                if(requestWrapper.getContentLength() > 0){
                    beforeReqLog.append("===> Body: {}\n");
                    String reqBosyStr = new String(requestWrapper.getContentAsByteArray(), requestWrapper.getCharacterEncoding());
                    beforeReqArgs.add(reqBosyStr.replaceAll("\\s+", ""));
                }
            }

            logIngHeaders(requestWrapper, beforeReqLog, beforeReqArgs);

        }catch (Exception e){
            log.info("printResponseLog error",e);
        }
        log.info(StrUtil.format(beforeReqLog.toString(),beforeReqArgs.toArray()));
    }



    /**
     * 记录请求头
     *
     * @param request       HttpServletRequest
     * @param beforeReqLog  StringBuilder
     * @param beforeReqArgs beforeReqArgs
     */
    public void logIngHeaders(HttpServletRequest request,
                              StringBuilder beforeReqLog, List<Object> beforeReqArgs) {
        // 打印请求头
        Enumeration<String> headers = request.getHeaderNames();
        beforeReqLog.append("===> Headers: \n");
        JSONObject jsonObject = new JSONObject();
        while (headers.hasMoreElements()) {
            String headerName = headers.nextElement();
            String headerValue = request.getHeader(headerName);
            jsonObject.put(headerName, headerValue);
        }
        if(!jsonObject.isEmpty()){
            beforeReqLog.append("     {}");
            beforeReqArgs.add(jsonObject.toJSONString());
        }

    }
    private boolean isBinaryContent(final HttpServletRequest request) {
        if (request.getContentType() == null) {
            return false;
        }
        return request.getContentType().startsWith("image")
                || request.getContentType().startsWith("video")
                || request.getContentType().startsWith("audio");
    }

    private boolean isMultipart(final HttpServletRequest request) {
        return request.getContentType() != null
                && request.getContentType().startsWith("multipart/form-data");
    }

    private void printResponseLog(ContentCachingRequestWrapper requestWrapper, ContentCachingResponseWrapper responseWrapper,long tookMs) {
        StringBuilder afterReqLog = new StringBuilder(200);
        // 日志参数
        List<Object> afterReqArgs = new ArrayList<>();
        try {

            afterReqLog.append("\n===========================  Response  ============================\n");
            //过滤器执行完毕，读取响应体
            byte[] resBody = responseWrapper.getContentAsByteArray();

            if(responseWrapper.getContentSize() >0){
                //将响应体转换为字符串
                String resBodyStr = new String(resBody, responseWrapper.getCharacterEncoding());
                // 打印返回结构体
                afterReqLog.append("<===Result: {}\n");
                afterReqArgs.add(JsonUtil.objToJson(resBodyStr));
            }

            afterReqLog.append("<==={}: {} ({} ms)");
            afterReqArgs.add(requestWrapper.getRequestURI());
            afterReqArgs.add(requestWrapper.getMethod());
            afterReqArgs.add(tookMs);
        }catch (Exception e){
            log.info("printResponseLog error",e);
        }
        log.info(StrUtil.format(afterReqLog.toString(),afterReqArgs.toArray()));

    }

    @Override
    public void destroy() {

    }

}
